/*
 * $Id: THFTransaction.java,v 1.22 2012/02/13 17:04:36 navdeep_mahajan Exp $
 * 
 * Copyright (C) 2008 General Electric Company. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of the General
 * Electric Company (GE). You shall not disclose this software and shall use it
 * only in accordance with the terms of the license agreement you entered into
 * with GE.
 * 
 * GE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * NON-INFRINGEMENT. GE SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING, OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES.
 */
package com.ge.healthcare.autosc.processor.iptds.transaction;

import java.io.File;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;

import org.apache.regexp.RE;
import org.apache.regexp.RESyntaxException;
import org.springframework.util.Assert;

import com.ge.healthcare.autosc.common.ASCLogger;
import com.ge.healthcare.autosc.common.ASCUtil;
import com.ge.healthcare.autosc.common.database.dao.IRequestDefn2ProdiagBlockDAO;
import com.ge.healthcare.autosc.common.database.dao.IRequestLoginDetailsDAO;
import com.ge.healthcare.autosc.common.database.dao.IRequestPropertiesDAO;
import com.ge.healthcare.autosc.common.database.pojo.ActRequest;
import com.ge.healthcare.autosc.common.database.pojo.ActRequestList;
import com.ge.healthcare.autosc.common.database.pojo.ActRequestProperties;
import com.ge.healthcare.autosc.common.database.pojo.ActTask;
import com.ge.healthcare.autosc.common.dbLogger.ContactLogger;
import com.ge.healthcare.autosc.common.dbLogger.StepLogger;
import com.ge.healthcare.autosc.common.util.TimeFormatStandard;
import com.ge.healthcare.autosc.processor.IPTDSCodeConstants;
import com.ge.healthcare.autosc.processor.exception.ClientDrvCommandException;
import com.ge.healthcare.autosc.processor.exception.IPTDSException;
import com.ge.healthcare.autosc.processor.iip.comm.ClientDrvCommand;
import com.ge.healthcare.autosc.processor.iptds.IPTDSSystemInfo;

/**
 * 
 * @author 212042946
 * 
 */
public class THFTransaction extends BaseTransaction implements ITHFTransaction {

	private IRequestPropertiesDAO requestPropertiesDAO = null;
	private IRequestLoginDetailsDAO requestLoginDetailsDAO = null;
	private IRequestDefn2ProdiagBlockDAO reqDef2ProDiagBlkDAO = null;

	public THFTransaction() {
	}

	/**
	 * In Spring framework, this is customized init method
	 */
	public void init() {
		super.init();
		Assert.notNull(
				requestPropertiesDAO,
				"ActRequestPropertiesDAO object is not defined. "
						+ "Please use setActRequestPropertiesDAO to provide one");
	}

	public void setActRequestPropertiesDAO(
			IRequestPropertiesDAO requestPropertiesDAO) {
		this.requestPropertiesDAO = requestPropertiesDAO;
	}

	public void setActRequestLoginDetailsDAO(
			IRequestLoginDetailsDAO requestLoginDetailsDAO) {
		this.requestLoginDetailsDAO = requestLoginDetailsDAO;
	}

	/**
	 * Execute THF transaction. This method assumes the caller is CHFTransaction
	 * instance and it will do the following.
	 * <p>
	 * <ul>
	 * <li>Create Request List (if needed), request, task and file transfer
	 * record.</li>
	 * <li>Attach CHF file transfer record to the request.</li>
	 * <li>Transfer TDF file from the remote system, if any.</li>
	 * <li>Close the THF transaction to the remote system</li>
	 * </ul>
	 * <p>
	 * File transfer task is created with the type of service request from the
	 * remote system. The code of the task and the transaction closing status in
	 * the remote system is determined based upon if the THF transaction is
	 * successfully done. For example, if there is data file for the transaction
	 * but it failed to reach the BO for whatever reason, the file transfer task
	 * for the request would be marked with status representing the failure. The
	 * THF would also be closed with failure in the remote system as well.
	 * 
	 * @param requestList
	 *            ActRequestList object
	 * @param chfFile
	 *            contain CHF file properties
	 * @param thf
	 *            contain THF file properties
	 * @param systemInfo
	 *            information regarding to the remote system
	 * @param clientDrv
	 *            ClientDrvCommand
	 * @throws IPTDSException
	 *             - Throw this exception to the caller means the caller should
	 *             stop IPTDS transaction because the cause of exception will
	 *             most likely fail other THF transactions as well. This
	 *             exception must turn stopper on.
	 */
	public boolean executeTransaction(ActRequestList requestList,
			ChfFile chfFile, ThfProperties thf, IPTDSSystemInfo systemInfo,
			ClientDrvCommand clientDrv) throws IPTDSException {
		String methodName = "executeTransaction";
		exitCode.set(IPTDSCodeConstants.PRC_P_TDS_000);
		String tranId = thf.getTransId();
		boolean tranStatus = false;
		ActRequest request = null;
		try {
			ASCLogger.info(
					this.getClass(),
					methodName,
					"Execute THF transaction for system "
							+ systemInfo.getSystemId());

			StepLogger.logStartTime();
			ContactLogger.copyLogsToStepLogger();
			chfFile.copyLogsToStepLogger();
			request = createRequest(requestList, systemInfo, chfFile, thf);

			/*
			 * Only process the THF when the THF information is valid
			 */
			if (isRequestValid(request)) {
				if (chfFile.isValid()) {
					StepLogger.addStepLog("chf " + chfFile.getFilename()
							+ " is valid");
					if (this.isAllowedForTransfer(request, thf)) {
						getTDF(requestList, request, chfFile, thf,
								systemInfo.getFTPCommand(), clientDrv);
						exitCode.set(IPTDSCodeConstants.PRC_S_TDS_000);
					} else {
						StepLogger.addStepLog("thf " + thf.getSdfnrNameNoPath()
								+ " is not allowed for transfer");
						exitCode.set(IPTDSCodeConstants.PRC_F_TDS_021);
					}
					ASCLogger.info(this.getClass(), methodName, "Exit code: "
							+ exitCode.get());
					tranStatus = true;
				} else {
					// Invalid CHF, should not fetch TDF and therefore fail the
					// task
					exitCode.set(IPTDSCodeConstants.PRC_F_TDS_010);
					ASCLogger.error(this.getClass(), methodName, "chf entry "
							+ chfFile.getFilename() + " is invalid", true);
					ASCLogger.error(this.getClass(), methodName,
							"Invalid CHF code: " + exitCode.get());
				}
			}
		} catch (IPTDSException iptdse) {
			ASCLogger.error(this.getClass(), methodName,
					"IPTDSException code: " + iptdse.getErrorCode());
			ASCLogger.error(this.getClass(), methodName, iptdse.getMessage(),
					iptdse, true);
			exitCode.set(iptdse.getErrorCode());
		} catch (Throwable e) {
			// Catch throwable because we want to log the problem and then
			// continue
			ASCLogger.error(this.getClass(), methodName,
					"Exception occurred performing THF transaction " + tranId,
					e, true);
			exitCode.set(IPTDSCodeConstants.PRC_F_TDS_018);
		} finally {
			closeTransaction(request, clientDrv, tranId, tranStatus);
		}
		return tranStatus;
	}

	/**
	 * Checks to see if the file name is in the blocked list or not
	 * @param request
	 * @param thf
	 * @return
	 */
	private boolean isAllowedForTransfer(ActRequest request, ThfProperties thf) {
		List<String> blockLst = reqDef2ProDiagBlkDAO
				.getProDiagBlockLstForRequestDefn(request.getRequestDefnId(), request.getIsoCode());
		if (ASCUtil.isNullOrEmptyList(blockLst)) {
			StepLogger
					.addStepLog("No Block list specified for request definition id:"
							+ request.getRequestDefnId());
			return true;
		}
		for (String pattern : blockLst) {
			try {
				RE reFilePattern = new RE(pattern);
				if (reFilePattern.match(thf.getSdfnrNameNoPath())) {
					return false;
				}
			} catch (RESyntaxException syntaxException) {
				ASCLogger.info(this.getClass(), "isAllowedForTransfer", String
						.format("Pattern %s is invalid and is being ignored",
								pattern));
			}
		}
		return true;
	}

	/*
	 * Walk through each THF content and retrieve data file REMARK: Autosc G2
	 * assumes the USN in the CHF level is the same as the USN within the THF
	 * portion in the same CFH file. A system has been selected and verification
	 * earlier based upon the client ip address and the usn from the CHF file.
	 */
	private File getTDF(ActRequestList requestList, ActRequest request,
			ChfFile chfFile, ThfProperties thf, IPTDSFTPCommand ftpCommand,
			ClientDrvCommand clientDrv) throws IPTDSException {
		ASCLogger.info("Start retrieving TDF file for the THF entry\n"
				+ thf.toString() + "\n");
		String tdfName = thf.getTdfName();
		StepLogger.addStepLog("Retrieving TDS file " + tdfName);
		String targetPath = chfFile.getProperty(ChfFile.PATHTOASC);
		File localFile = null;
		boolean ftpStatus = false;
		Date ftpStartDate = TimeFormatStandard.getNowMillis();
		String tranId = thf.getTransId();
		try {
			if ((tdfName != null) && (targetPath != null)) {
				// Create request level directory
				String jobDir = getRequestDir(requestList, request);
				if (new File(jobDir).mkdirs() == true) {
					localFile = new File(jobDir, tdfName);
					File remoteFile = new File(targetPath, tdfName);
					ftpCommand.getRemoteFile(localFile, remoteFile, clientDrv);
					ftpStatus = true;
					ASCLogger.info(
							new StringBuffer().append("TDF file ")
									.append(tdfName)
									.append(" was successfully transfer to ")
									.append(localFile.getPath()).append(".")
									.append("Transaction ID: ").append(tranId)
									.toString(), true);
					localFile = renameToSdfnrFile(thf, localFile);
				} else {
					String errMsg = new StringBuffer()
							.append("Failed creating directory ")
							.append(jobDir).append(" for tdf file ")
							.append(tdfName).toString();
					StepLogger.addStepLog(errMsg.toString());
					throw new IPTDSException(errMsg,
							IPTDSCodeConstants.PRC_F_TDS_016, true);
				}
			} else {
				String msg = "No tdf file for this transaction. Transaction ID: "
						+ tranId;
				StepLogger.addStepLog(msg);
				ASCLogger.debug(msg);
			}
		} finally {
			Date ftpEndDate = TimeFormatStandard.getNowMillis();
			if (!ASCUtil.isNullOrEmptyObject(localFile)) {
				createFileTransferDetails(request.getRequestId(), localFile,
						ftpStartDate, ftpEndDate, ftpStatus, false);
			}
		}
		return localFile;
	}

	private ActRequest createRequest(ActRequestList requestList,
			IPTDSSystemInfo systemInfo, ChfFile chf, ThfProperties thf) {
		// If requestList has no id, commit it.
		if (requestList.getRequestListId() == null) {
			requestListDAO.save(requestList);
		}

		ActRequest request = createRequest("IPTDS", thf.getServiceId(),
				thf.getCreatedBy(), requestList, systemInfo);
		ASCLogger.info(this.getClass(), "createRequest", "Requset created :\n"
				+ request);
		createRequestLoginDetails(request, systemInfo);
		createRequestProperties(request, thf);
		// Attached CHF file to request
		createFileTransferDetailsForChf(requestList, request, chf);
		createTask(request);
		return request;
	}

	private void createRequestLoginDetails(ActRequest request,
			IPTDSSystemInfo systemInfo) {
		Properties logins = systemInfo.getLoginInfo();
		if ((logins != null) && (!logins.isEmpty())) {
			Enumeration<?> usernames = logins.propertyNames();
			while (usernames.hasMoreElements()) {
				String username = (String) usernames.nextElement();
				String passcode = (String) logins.getProperty(username);
				requestLoginDetailsDAO.addRequestLoginDetails(
						request.getRequestId(), username, passcode);
			}
		} else {
			ASCLogger.info(this.getClass(), "createRequestLoginDetails",
					"System " + request.getSystemId()
							+ " has no login information.", true);
		}
	}

	private void createRequestProperties(ActRequest request, ThfProperties thf) {
		if (!ASCUtil.isNullOrEmpty(thf.getServiceId())) {
			requestPropertiesDAO.addRequestProperties(request.getRequestId(),
					ActRequestProperties.SERVICE_ID, thf.getServiceId());
		}
		if (!ASCUtil.isNullOrEmpty(thf.getCreatedBy())) {
			requestPropertiesDAO.addRequestProperties(request.getRequestId(),
					ActRequestProperties.CREATED_BY, thf.getCreatedBy());
		}
		if (!ASCUtil.isNullOrEmpty(thf.getTdfName())) {
			requestPropertiesDAO.addRequestProperties(request.getRequestId(),
					ActRequestProperties.TDF_FILE, thf.getSdfnrNameNoPath());
		}
		if (!ASCUtil.isNullOrEmpty(thf.getSdfnr())) {
			requestPropertiesDAO.addRequestProperties(request.getRequestId(),
					ActRequestProperties.SDFNR, thf.getSdfnr());
		}
		if (!ASCUtil.isNullOrEmpty(thf.getOId())) {
			requestPropertiesDAO.addRequestProperties(request.getRequestId(),
					ActRequestProperties.O_ID, thf.getOId());
		}
		if (!ASCUtil.isNullOrEmpty(thf.getTdfSize())) {
			requestPropertiesDAO.addRequestProperties(request.getRequestId(),
					ActRequestProperties.TDF_SIZE, thf.getTdfSize());
		}
	}

	private void createFileTransferDetailsForChf(ActRequestList requestList,
			ActRequest request, ChfFile chf) {
		/*
		 * Move CHF file into request list directory. If the move failed, then
		 * do not create the file transfer details so that cleanup process would
		 * not delete all request directories under where the CHF file is
		 * located.
		 */
		String requestListDir = this.getRequestListDir(requestList);
		if (chf.move(requestListDir) == false) {
			ASCLogger.warn(this.getClass(), "createFileTransferDetailsForChf",
					"Unable to move CHF file from " + chf.getFilePath()
							+ " to request list directory " + requestListDir
							+ ". FileTransferDetails will not be created");
			return;
		}
		createFileTransferDetails(request.getRequestId(), chf.getFile(),
				chf.getStartTime(), chf.getEndTime(), true, true);
	}

	private void closeTransaction(ActRequest request,
			ClientDrvCommand clientDrv, String tranId, boolean status)
			throws IPTDSException {

		/*
		 * Only close transaction if CHF is valid. If transaction is not valid
		 * because of request definition not found, AutoSC just want to fail
		 * that transaction (i.e the THF request), not the whole CHF.
		 */
		if (!IPTDSCodeConstants.PRC_F_TDS_010.equals(exitCode.get())) {
			closeTransaction(clientDrv, tranId, status);
		}

		if (request != null) {
			ActTask task = request.getTask(0);
			if (task != null) {
				ASCLogger.info(this.getClass(), "closeTransaction",
						"exitCode: " + exitCode.get());
				task.setTaskCode(codeDetailsDAO, exitCode.get());
				taskDAO.save(task);
				StepLogger.logEndTime(task.getTaskId(), task.getObjectType(),
						task.getCodeId());
			} else {
				// This is for invalid request
				StepLogger.logEndTime(request.getRequestId(),
						request.getObjectType(), request.getCodeId());
			}
		}
	}

	private void closeTransaction(ClientDrvCommand clientDrv, String tranId,
			boolean status) throws IPTDSException {
		String reply = null;
		try {
			if (status == true) {
				StepLogger
						.addStepLog("Close transaction with succeed. Transaction ID: "
								+ tranId);
				reply = clientDrv.executeTransSucceedCommand(tranId);
			} else {
				StepLogger
						.addStepLog("Close transaction with failure. Transaction ID: "
								+ tranId);
				reply = clientDrv.executeTransFailCommand(tranId);
			}
			if ((reply == null) || (reply.startsWith("0") != true)) {
				// Something wrong at the IIP side closing the transaction
				String errMsg = new StringBuffer().append(
						"Failed closing THF transaction. Transaction Id: "
								+ tranId).toString();
				StepLogger.addStepLog(errMsg.toString());
				throw new IPTDSException(errMsg,
						IPTDSCodeConstants.PRC_F_TDS_014, true);
			}
		} catch (ClientDrvCommandException cdce) {
			String errMsg = new StringBuffer()
					.append("Failed executing command to close transaction. Transaction Id: ")
					.append(tranId).toString();
			StepLogger.addStepLog(errMsg.toString());
			throw new IPTDSException(errMsg, cdce,
					IPTDSCodeConstants.PRC_F_TDS_014, true);
		}
	}

	private File renameToSdfnrFile(ThfProperties thf, File tdfFile) {
		File sdfnrFile = tdfFile;
		if (tdfFile.exists()) {
			String sdfnrName = thf.getSdfnrNameNoPath();
			if (!ASCUtil.isNullOrEmpty(sdfnrName)) {
				sdfnrFile = new File(tdfFile.getParent(), sdfnrName);
				ASCLogger.info(
						new StringBuffer().append("Rename file ")
								.append(tdfFile.getPath()).append(" to ")
								.append(sdfnrFile.getPath()).toString(), true);
				tdfFile.renameTo(sdfnrFile);
			}
		}
		return sdfnrFile;
	}

	private String getRequestDir(ActRequestList requestList, ActRequest request) {
		String jobDir = getRequestListDir(requestList);
		jobDir = ASCUtil.getSubDir(jobDir, request.getRequestId());
		return jobDir;
	}

	private String getRequestListDir(ActRequestList requestList) {
		String jobDir = ASCUtil.getJobDir(requestList.getBatchId());
		jobDir = ASCUtil.getSubDir(jobDir, requestList.getRequestListId());
		return jobDir;
	}

	/**
	 * @return the reqDef2ProDiagBlkDAO
	 */
	public IRequestDefn2ProdiagBlockDAO getReqDef2ProDiagBlkDAO() {
		return reqDef2ProDiagBlkDAO;
	}

	/**
	 * @param reqDef2ProDiagBlkDAO
	 *            the reqDef2ProDiagBlkDAO to set
	 */
	public void setReqDef2ProDiagBlkDAO(
			IRequestDefn2ProdiagBlockDAO reqDef2ProDiagBlkDAO) {
		this.reqDef2ProDiagBlkDAO = reqDef2ProDiagBlkDAO;
	}
}
